"""
Webhook通知系统 - 事件驱动版本

这个模块实现了基于事件驱动的Webhook通知系统：
- 订阅任务完成事件
- 执行Webhook回调
- 支持多种通知方式
- 提供重试和错误处理机制
"""

import asyncio
import logging
import time
import json
import hashlib
import hmac
from typing import Dict, Any, List, Optional, Callable
from dataclasses import dataclass, asdict
from enum import Enum
import aiohttp

from event_bus import EventBus, EventType, EventHandler, Event, get_event_bus

logger = logging.getLogger(__name__)


class WebhookStatus(Enum):
    """Webhook状态枚举"""
    PENDING = "pending"
    SENDING = "sending"
    SUCCESS = "success"
    FAILED = "failed"
    RETRYING = "retrying"
    DEAD_LETTER = "dead_letter"


class NotificationType(Enum):
    """通知类型枚举"""
    WEBHOOK = "webhook"
    EMAIL = "email"
    SMS = "sms"
    SLACK = "slack"
    DISCORD = "discord"
    TEAMS = "teams"


@dataclass
class WebhookMessage:
    """Webhook消息数据类"""
    id: str
    task_id: str
    callback_url: str
    payload: Dict[str, Any]
    headers: Dict[str, str]
    status: WebhookStatus
    created_at: float
    updated_at: float
    retry_count: int = 0
    max_retries: int = 3
    next_retry_at: Optional[float] = None
    error_message: Optional[str] = None
    signature: Optional[str] = None


@dataclass
class NotificationConfig:
    """通知配置"""
    webhook_timeout: int = 10  # 秒
    max_retries: int = 3
    retry_delays: List[int] = None  # [5, 15, 45] 秒
    signature_secret: str = "default_secret"
    signature_header: str = "X-Signature"
    allowed_ips: List[str] = None  # 空列表表示允许所有IP
    enable_signature_verification: bool = True
    enable_ip_whitelist: bool = False
    
    def __post_init__(self):
        if self.retry_delays is None:
            self.retry_delays = [5, 15, 45]  # 指数退避
        if self.allowed_ips is None:
            self.allowed_ips = []


class NotificationHandler(ABC):
    """通知处理器抽象基类"""
    
    @abstractmethod
    async def send_notification(self, 
                               task_id: str,
                               payload: Dict[str, Any],
                               config: Dict[str, Any]) -> bool:
        """发送通知"""
        pass
    
    @abstractmethod
    def get_notification_type(self) -> NotificationType:
        """获取通知类型"""
        pass


class WebhookNotificationHandler(NotificationHandler):
    """Webhook通知处理器"""
    
    def __init__(self, config: NotificationConfig):
        self.config = config
        self.logger = logging.getLogger(f"{__name__}.WebhookHandler")
    
    def get_notification_type(self) -> NotificationType:
        return NotificationType.WEBHOOK
    
    async def send_notification(self, 
                               task_id: str,
                               payload: Dict[str, Any],
                               config: Dict[str, Any]) -> bool:
        """发送Webhook通知"""
        callback_url = config.get("callback_url")
        if not callback_url:
            self.logger.error(f"任务 {task_id} 缺少callback_url")
            return False
        
        try:
            # 准备头部
            headers = config.get("headers", {})
            headers.setdefault("Content-Type", "application/json")
            headers.setdefault("User-Agent", "GPU-Auto-Scaling-System/2.0")
            
            # 生成签名
            if self.config.enable_signature_verification:
                signature = self._generate_signature(payload)
                headers[self.config.signature_header] = signature
            
            # 发送请求
            timeout = aiohttp.ClientTimeout(total=self.config.webhook_timeout)
            
            async with aiohttp.ClientSession(timeout=timeout) as session:
                async with session.post(
                    callback_url,
                    json=payload,
                    headers=headers
                ) as response:
                    
                    if response.status == 200:
                        self.logger.info(f"Webhook发送成功: {task_id} -> {callback_url}")
                        return True
                    else:
                        error_text = await response.text()
                        self.logger.warning(
                            f"Webhook发送失败: {task_id} -> {callback_url}, "
                            f"状态码: {response.status}, 错误: {error_text}"
                        )
                        return False
                        
        except asyncio.TimeoutError:
            self.logger.warning(f"Webhook发送超时: {task_id} -> {callback_url}")
            return False
        except Exception as e:
            self.logger.error(f"Webhook发送异常: {task_id} -> {callback_url}, 错误: {e}")
            return False
    
    def _generate_signature(self, payload: Dict[str, Any]) -> str:
        """生成签名"""
        payload_str = json.dumps(payload, sort_keys=True)
        signature = hmac.new(
            self.config.signature_secret.encode(),
            payload_str.encode(),
            hashlib.sha256
        ).hexdigest()
        return f"sha256={signature}"


class EmailNotificationHandler(NotificationHandler):
    """邮件通知处理器"""
    
    def get_notification_type(self) -> NotificationType:
        return NotificationType.EMAIL
    
    async def send_notification(self, 
                               task_id: str,
                               payload: Dict[str, Any],
                               config: Dict[str, Any]) -> bool:
        """发送邮件通知"""
        # TODO: 实现邮件发送逻辑
        self.logger.info(f"邮件通知: {task_id}")
        return True


class SlackNotificationHandler(NotificationHandler):
    """Slack通知处理器"""
    
    def get_notification_type(self) -> NotificationType:
        return NotificationType.SLACK
    
    async def send_notification(self, 
                               task_id: str,
                               payload: Dict[str, Any],
                               config: Dict[str, Any]) -> bool:
        """发送Slack通知"""
        webhook_url = config.get("webhook_url")
        if not webhook_url:
            self.logger.error(f"任务 {task_id} 缺少Slack webhook_url")
            return False
        
        try:
            # 构建Slack消息
            slack_message = {
                "text": f"任务 {task_id} 处理完成",
                "attachments": [
                    {
                        "color": "good" if payload.get("status") == "completed" else "danger",
                        "fields": [
                            {
                                "title": "任务ID",
                                "value": task_id,
                                "short": True
                            },
                            {
                                "title": "状态",
                                "value": payload.get("status", "unknown"),
                                "short": True
                            },
                            {
                                "title": "处理时长",
                                "value": f"{payload.get('processing_duration', 0):.2f}秒",
                                "short": True
                            }
                        ]
                    }
                ]
            }
            
            # 如果有结果URL，添加到消息中
            if payload.get("result_url"):
                slack_message["attachments"][0]["fields"].append({
                    "title": "结果URL",
                    "value": payload["result_url"],
                    "short": False
                })
            
            # 发送到Slack
            timeout = aiohttp.ClientTimeout(total=10)
            async with aiohttp.ClientSession(timeout=timeout) as session:
                async with session.post(webhook_url, json=slack_message) as response:
                    if response.status == 200:
                        self.logger.info(f"Slack通知发送成功: {task_id}")
                        return True
                    else:
                        self.logger.warning(f"Slack通知发送失败: {task_id}, 状态码: {response.status}")
                        return False
                        
        except Exception as e:
            self.logger.error(f"Slack通知发送异常: {task_id}, 错误: {e}")
            return False


class WebhookNotificationSystem(EventHandler):
    """Webhook通知系统 - 事件驱动版本"""
    
    def __init__(self, 
                 config: Optional[NotificationConfig] = None,
                 event_bus: Optional[EventBus] = None):
        self.config = config or NotificationConfig()
        self.event_bus = event_bus or get_event_bus()
        
        # 通知处理器
        self.handlers: Dict[NotificationType, NotificationHandler] = {
            NotificationType.WEBHOOK: WebhookNotificationHandler(self.config),
            NotificationType.EMAIL: EmailNotificationHandler(),
            NotificationType.SLACK: SlackNotificationHandler(),
        }
        
        # 统计信息
        self.total_sent = 0
        self.total_failed = 0
        self.total_retries = 0
        self.last_activity_time = 0.0
        
        # 订阅相关事件
        self.event_bus.subscribe(self, priority=5)
        
        self.logger = logging.getLogger(__name__)
        self.logger.info("Webhook通知系统已初始化")
    
    def can_handle(self, event_type: EventType) -> bool:
        """判断是否能处理指定类型的事件"""
        return event_type in {
            EventType.TASK_COMPLETED,
            EventType.TASK_FAILED,
            EventType.TASK_CANCELLED,
            EventType.TASK_TIMEOUT
        }
    
    async def handle(self, event: Event) -> None:
        """处理事件"""
        try:
            task_id = event.data.get("task_id")
            if not task_id:
                self.logger.warning("事件缺少task_id")
                return
            
            # 构建通知载荷
            payload = self._build_notification_payload(event)
            
            # 获取通知配置
            notification_configs = event.data.get("notification_configs", [])
            if not notification_configs:
                # 如果没有配置，使用默认的Webhook配置
                notification_configs = [{
                    "type": "webhook",
                    "callback_url": event.data.get("callback_url")
                }]
            
            # 发送通知
            await self._send_notifications(task_id, payload, notification_configs)
            
            self.last_activity_time = time.time()
            
        except Exception as e:
            self.logger.error(f"处理通知事件时出错: {e}", exc_info=True)
    
    def _build_notification_payload(self, event: Event) -> Dict[str, Any]:
        """构建通知载荷"""
        payload = {
            "task_id": event.data.get("task_id"),
            "status": event.type.value,
            "timestamp": event.timestamp.isoformat(),
            "source": event.source,
            "correlation_id": event.correlation_id
        }
        
        # 根据事件类型添加特定信息
        if event.type == EventType.TASK_COMPLETED:
            payload.update({
                "result_url": event.data.get("result_url"),
                "processing_duration": event.data.get("processing_duration"),
                "server_id": event.data.get("server_id")
            })
        elif event.type in {EventType.TASK_FAILED, EventType.TASK_TIMEOUT}:
            payload.update({
                "error_message": event.data.get("error_message"),
                "processing_duration": event.data.get("processing_duration"),
                "server_id": event.data.get("server_id")
            })
        elif event.type == EventType.TASK_CANCELLED:
            payload.update({
                "reason": event.data.get("reason", "任务被取消")
            })
        
        # 添加元数据
        if event.data.get("metadata"):
            payload["metadata"] = event.data["metadata"]
        
        return payload
    
    async def _send_notifications(self, 
                                 task_id: str,
                                 payload: Dict[str, Any],
                                 notification_configs: List[Dict[str, Any]]) -> None:
        """发送通知"""
        for config in notification_configs:
            notification_type_str = config.get("type", "webhook")
            
            try:
                notification_type = NotificationType(notification_type_str)
                handler = self.handlers.get(notification_type)
                
                if not handler:
                    self.logger.warning(f"不支持的通知类型: {notification_type_str}")
                    continue
                
                # 发送通知
                success = await handler.send_notification(task_id, payload, config)
                
                if success:
                    self.total_sent += 1
                    # 发布通知发送成功事件
                    await self.event_bus.publish(
                        EventType.WEBHOOK_SENT,
                        source="webhook_notification_system",
                        data={
                            "task_id": task_id,
                            "notification_type": notification_type_str,
                            "config": config
                        }
                    )
                else:
                    self.total_failed += 1
                    # 发布通知发送失败事件
                    await self.event_bus.publish(
                        EventType.WEBHOOK_FAILED,
                        source="webhook_notification_system",
                        data={
                            "task_id": task_id,
                            "notification_type": notification_type_str,
                            "config": config,
                            "error": "发送失败"
                        }
                    )
                    
                    # 如果配置了重试，安排重试
                    if config.get("enable_retry", True):
                        await self._schedule_retry(task_id, payload, config)
                
            except Exception as e:
                self.logger.error(f"发送通知失败: {task_id}, 类型: {notification_type_str}, 错误: {e}")
                self.total_failed += 1
    
    async def _schedule_retry(self, 
                             task_id: str,
                             payload: Dict[str, Any],
                             config: Dict[str, Any]) -> None:
        """安排重试"""
        retry_count = config.get("retry_count", 0)
        max_retries = config.get("max_retries", self.config.max_retries)
        
        if retry_count >= max_retries:
            self.logger.error(f"通知重试次数已达上限: {task_id}")
            return
        
        # 计算重试延迟
        retry_delay = self.config.retry_delays[min(
            retry_count, 
            len(self.config.retry_delays) - 1
        )]
        
        # 更新重试计数
        config["retry_count"] = retry_count + 1
        
        # 发布重试事件
        await self.event_bus.publish(
            EventType.WEBHOOK_RETRY,
            source="webhook_notification_system",
            data={
                "task_id": task_id,
                "payload": payload,
                "config": config,
                "retry_delay": retry_delay,
                "retry_count": retry_count + 1
            }
        )
        
        self.total_retries += 1
        self.logger.info(f"已安排通知重试: {task_id}, 延迟: {retry_delay}秒, 重试次数: {retry_count + 1}")
    
    def register_handler(self, 
                        notification_type: NotificationType,
                        handler: NotificationHandler) -> None:
        """注册通知处理器"""
        self.handlers[notification_type] = handler
        self.logger.info(f"已注册通知处理器: {notification_type.value}")
    
    def unregister_handler(self, notification_type: NotificationType) -> None:
        """取消注册通知处理器"""
        if notification_type in self.handlers:
            del self.handlers[notification_type]
            self.logger.info(f"已取消注册通知处理器: {notification_type.value}")
    
    async def send_manual_notification(self, 
                                      task_id: str,
                                      notification_type: NotificationType,
                                      payload: Dict[str, Any],
                                      config: Dict[str, Any]) -> bool:
        """手动发送通知"""
        handler = self.handlers.get(notification_type)
        if not handler:
            self.logger.error(f"不支持的通知类型: {notification_type.value}")
            return False
        
        try:
            success = await handler.send_notification(task_id, payload, config)
            if success:
                self.total_sent += 1
            else:
                self.total_failed += 1
            return success
        except Exception as e:
            self.logger.error(f"手动发送通知失败: {task_id}, 错误: {e}")
            self.total_failed += 1
            return False
    
    async def get_statistics(self) -> Dict[str, Any]:
        """获取统计信息"""
        return {
            "total_sent": self.total_sent,
            "total_failed": self.total_failed,
            "total_retries": self.total_retries,
            "last_activity_time": self.last_activity_time,
            "registered_handlers": list(self.handlers.keys()),
            "config": {
                "webhook_timeout": self.config.webhook_timeout,
                "max_retries": self.config.max_retries,
                "retry_delays": self.config.retry_delays,
                "enable_signature_verification": self.config.enable_signature_verification,
                "enable_ip_whitelist": self.config.enable_ip_whitelist
            }
        }
